﻿using FastEmit;
using System.Text;
using System.Text.Json;

namespace VictoriaMetrics.Client
{
    public class MetricsWriter
    {
        #region static

        public static readonly byte[] N = [(byte)'\n'];

        #endregion

        private readonly Stream _stream;
        private readonly CancellationToken _token;
        public MetricsWriter(Stream stream, CancellationToken token = default)
        {
            _stream = stream;
            _token = token;
        }

#if NET462
        public Task WriteNAsync()
        {
            return _stream.WriteAsync(N, 0, 1, _token);
        }
#else
        public ValueTask WriteNAsync()
        {
            return _stream.WriteAsync(N, _token);
        }
#endif


#if NET462
        public Task WriteJsonlineAsync(string line)
        {
            var bytes = Encoding.UTF8.GetBytes(line);
            return _stream.WriteAsync(bytes, 0, bytes.Length, _token);
        }
#else
        public ValueTask WriteJsonlineAsync(string line)
        {
            var bytes = Encoding.UTF8.GetBytes(line);
            return _stream.WriteAsync(bytes, _token);
        }
#endif

        public Task WriteMetricsAsync(MetricsEntity metrics)
        {
            return JsonSerializer.SerializeAsync(_stream, metrics, VictoriaMetricsClient.JsonOptionsMs, _token);
        }

        public Task WritePointAsync(MetricsPointData point)
        {
            if (string.IsNullOrEmpty(point.MetricName))
            {
                throw new Exception("MetricName is null or empty.");
            }

            if (point.Metric == null || point.Metric.Count == 0)
            {
                throw new Exception("Metric is null or empty.");
            }

            if (!point.Metric.ContainsKey("__name__"))
            {
                point.Metric.Add("__name__", point.MetricName);
            }

            if (point.Time == null)
            {
                point.Time = DateTimeOffset.Now;
            }

            var par = new
            {
                metric = point.Metric,
                values = new double[] { point.Value },
                timestamps = new long[] { point.Time.Value.ToUnixTimeMilliseconds() }
            };

            return JsonSerializer.SerializeAsync(_stream, par, VictoriaMetricsClient.JsonOptionsMs, _token);
        }

        public async Task WriteEntityAsync<T>(string metricName, T item) where T : class
        {
            var t = typeof(T);
            if (t == typeof(object))
            {
                item.GetType();
            }
            var accssor = FastEmitFactory.GetTypeAccessor(t);
            var ch = CacheHelper.GetCache(t);
            var i = 0;

            foreach (var field in ch.FieldKeys)
            {
                i++;
                if (i > 1)
                {
                    await WriteNAsync();
                }

                var entity = new MetricsEntity(metricName);
                foreach (var tag in ch.TagKeys)
                {
                    var tagVal = (string)accssor.GetValue(item, tag);
                    if (string.IsNullOrEmpty(tagVal))
                    {
                        throw new Exception($"{t.FullName}==>{tag}==>is null or empty.");
                    }
                    entity.metric.Add(tag, tagVal);
                }

                var fieldVal = accssor.GetValue(item, field);
                entity.metric.Add("_field", field);
                entity.values.Add(Convert.ToDouble(fieldVal));

                var timeVal = accssor.GetValue(item, ch.TimeKey);
                if (timeVal == null)
                {
                    entity.timestamps.Add(DateTimeOffset.Now.ToUnixTimeMilliseconds());
                }
                else
                {
                    entity.timestamps.Add(((DateTimeOffset)timeVal).ToUnixTimeMilliseconds());
                }

                await JsonSerializer.SerializeAsync(_stream, entity, VictoriaMetricsClient.JsonOptionsMs, _token);
            }
        }
    }
}
